/*
 * Copyright 2021 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <jni.h>

#include "include/core/SkColorSpace.h"
#include "include/core/SkM44.h"
#include "include/effects/SkGradientShader.h"
#include "modules/jetski/src/Utils.h"

namespace {
// Helper for common gradient data access.
class GradientData {
public:
    GradientData(JNIEnv *env, const jfloatArray &jcolors, const jfloatArray &jpos, jint jtm, jlong native_lm)
        : fEnv(env),
          fJColors(jcolors),
          fJPos(jpos),
          fColors(env->GetFloatArrayElements(jcolors, nullptr)),
          fPos(env->GetFloatArrayElements(jpos, nullptr)),
          fCount(env->GetArrayLength(jpos)),
          fTileMode(jetski::utils::TileMode(jtm)),
          fLocalMatrix(native_lm ? reinterpret_cast<const SkM44 *>(native_lm)->asM33() : SkMatrix())
    {
        SkASSERT(env->GetArrayLength(jcolors) == 4 * fCount);
    }

    ~GradientData()
    {
        fEnv->ReleaseFloatArrayElements(fJPos, fPos, 0);
        fEnv->ReleaseFloatArrayElements(fJColors, fColors, 0);
    }

    int count() const
    {
        return fCount;
    }

    const SkColor4f *colors() const
    {
        return reinterpret_cast<const SkColor4f *>(fColors);
    }
    const float *pos() const
    {
        return fPos;
    }
    const SkTileMode &tileMode() const
    {
        return fTileMode;
    }
    const SkMatrix &localMatrix() const
    {
        return fLocalMatrix;
    }

private:
    JNIEnv *fEnv;
    const jfloatArray &fJColors;
    const jfloatArray &fJPos;
    float *fColors;
    float *fPos;
    const int fCount;
    const SkTileMode fTileMode;
    const SkMatrix fLocalMatrix;
};

static jlong MakeLinear(JNIEnv *env, jobject, jfloat x0, jfloat y0, jfloat x1, jfloat y1, jfloatArray jcolors,
    jfloatArray jpos, jint jtm, jlong native_lm)
{
    const GradientData gdata(env, jcolors, jpos, jtm, native_lm);
    const SkPoint pts[] = {{x0, y0}, {x1, y1}};

    auto shader = SkGradientShader::MakeLinear(pts, gdata.colors(), nullptr, gdata.pos(), gdata.count(),
        gdata.tileMode(), 0, &gdata.localMatrix());

    return reinterpret_cast<jlong>(shader.release());
}

static jlong MakeRadial(JNIEnv *env, jobject, jfloat x, jfloat y, jfloat r, jfloatArray jcolors, jfloatArray jpos,
    jint jtm, jlong native_lm)
{
    const GradientData gdata(env, jcolors, jpos, jtm, native_lm);

    auto shader = SkGradientShader::MakeRadial({ x, y }, r, gdata.colors(), nullptr, gdata.pos(), gdata.count(),
        gdata.tileMode(), 0, &gdata.localMatrix());

    return reinterpret_cast<jlong>(shader.release());
}

static jlong MakeTwoPointConical(JNIEnv *env, jobject, jfloat x0, jfloat y0, jfloat r0, jfloat x1, jfloat y1, jfloat r1,
    jfloatArray jcolors, jfloatArray jpos, jint jtm, jlong native_lm)
{
    const GradientData gdata(env, jcolors, jpos, jtm, native_lm);

    auto shader = SkGradientShader::MakeTwoPointConical({ x0, y0 }, r0, { x1, y1 }, r1, gdata.colors(), nullptr,
        gdata.pos(), gdata.count(), gdata.tileMode(), 0, &gdata.localMatrix());

    return reinterpret_cast<jlong>(shader.release());
}

static jlong MakeSweep(JNIEnv *env, jobject, jfloat x, jfloat y, jfloat sa, jfloat ea, jfloatArray jcolors,
    jfloatArray jpos, jint jtm, jlong native_lm)
{
    const GradientData gdata(env, jcolors, jpos, jtm, native_lm);

    auto shader = SkGradientShader::MakeSweep(x, y, gdata.colors(), nullptr, gdata.pos(), gdata.count(),
        gdata.tileMode(), sa, ea, 0, &gdata.localMatrix());

    return reinterpret_cast<jlong>(shader.release());
}
} // namespace

int register_jetski_LinearGradient(JNIEnv *env)
{
    static const JNINativeMethod methods[] = {
        {"nMakeLinear", "(FFFF[F[FIJ)J", reinterpret_cast<void*>(MakeLinear)},
    };

    const auto clazz = env->FindClass("org/skia/jetski/LinearGradient");
    return clazz ? env->RegisterNatives(clazz, methods, std::size(methods)) : JNI_ERR;
}

int register_jetski_RadialGradient(JNIEnv *env)
{
    static const JNINativeMethod methods[] = {
        {"nMakeRadial", "(FFF[F[FIJ)J", reinterpret_cast<void*>(MakeRadial)},
    };

    const auto clazz = env->FindClass("org/skia/jetski/RadialGradient");
    return clazz ? env->RegisterNatives(clazz, methods, std::size(methods)) : JNI_ERR;
}

int register_jetski_TwoPointConicalGradient(JNIEnv *env)
{
    static const JNINativeMethod methods[] = {
        {"nMakeTwoPointConical", "(FFFFFF[F[FIJ)J", reinterpret_cast<void*>(MakeTwoPointConical)},
    };

    const auto clazz = env->FindClass("org/skia/jetski/TwoPointConicalGradient");
    return clazz ? env->RegisterNatives(clazz, methods, std::size(methods)) : JNI_ERR;
}

int register_jetski_SweepGradient(JNIEnv *env)
{
    static const JNINativeMethod methods[] = {
        {"nMakeSweep", "(FFFF[F[FIJ)J", reinterpret_cast<void*>(MakeSweep)},
    };

    const auto clazz = env->FindClass("org/skia/jetski/SweepGradient");
    return clazz ? env->RegisterNatives(clazz, methods, std::size(methods)) : JNI_ERR;
}
